// 安装并引入相关模块
import axios from 'axios'
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

const websocket = {

  wsQueue: {},

  /**
   * 生成一个用不重复的ID
   * @param { Number } randomLength
   */
  getUuiD(randomLength) {
    return Number(Math.random().toString().substr(2, randomLength) + Date.now()).toString(36)
  },

  /**
   * 添加新增加的ws对象
   * @param ws
   */
  addWs(ws) {
    this.wsQueue[ws.name] = ws;
  },

  /**
   * 移除ws对象
   * @param ws
   */
  removeWs(ws) {
    delete this.wsQueue[ws.name];
  },

  /**
   * 获取ws对象
   * @param name
   */
  getWs(name) {
    return this.wsQueue[name];
  },


  /**
   * 初始化websocket连接服务
   * 1：引入改组件并调用initWebSocket()，返回ws对象。
   * 2：需要显示调用ws对象中的connection()函数，并传入当前界面需要监听的topic以及回调函数，格式可以传入多个topic数据或则单个topic对象。
   *    >>>>>例如{topic:xxxx,callback:(msg)=>{}}或则[{topic:xxxx,callback:(msg)=>{}},{topic:xxxx,callback:(msg)=>{}}]
   * 3：如当前界面需要在跳转界面后需要将传输断开的话，需要显示调用ws对象的disconnect()函数，推出后会自动销毁定时器、连接对象等
   */
  initWebSocket(wsName) {
    let ws = this.getWs(wsName);
    if (ws) {
      return ws;
    }
    ws = {
      //订阅消息监听对象集合
      subscriber: {},
      //model对象，模块标识和更新语句柄=======>key：模块标识，value：标识对应的函数句柄或则回调函数，如：{modelName1：(msg) => {}, modelName2: (msg) => {}}
      smallModel: {},
      //socketJs对象
      socket: {},
      //当前ws对象是否可用状态
      connect: false,
      //stomp客户端对象
      stompClient: {},
      baseUrl: 'ueye/websocketJS',
      //初始化请求连接地址
      url: !axios.defaults.baseURL ? '/' : axios.defaults.baseURL.endsWith("/") ? axios.defaults.baseURL : axios.defaults.baseURL + '/',
      //ws协议对象名
      name: wsName ? wsName : 'ws_' + websocket.getUuiD(20),
      /**
       * 连接ws服务
       * @param subscribe 订阅topic
       * @param header 定义客户端的认证信息,按需求配置
       */
      connection(subscribe, header) {
        let that = this;
        if (!that.connect) {
          // 建立连接对象，连接服务端提供的通信接口，连接以后才可以订阅广播消息和个人消息
          this.socket = new SockJS(this.url + this.baseUrl);
          // 获取STOMP子协议的客户端对象
          this.stompClient = Stomp.over(this.socket);
          //去掉debugger模式
          this.stompClient.debug = null;
          /*
           * 1. 获取到stomp 子协议后，可以设置心跳连接时间，认证连接，主动断开连接
           * 2，连接心跳有的版本的stomp.js 是默认开启的，这里我们不管版本，手工设置
           * 3. 心跳是双向的，客户端开启心跳，必须要服务端支持心跳才行
           * 4. heartbeat.outgoing 表示客户端给服务端发送心跳的间隔时间
           * 5. 客户端接收服务端心跳的间隔时间，如果为0 表示客户端不接收服务端心跳
           */
          // this.stompClient.heartbeat.outgoing = 10000;
          // this.stompClient.heartbeat.incoming = 10000;
          // 向服务器发起websocket连接
          this.stompClient.connect(header ? header : {}, (frame) => {
            that.connect = true;
            console.log("当前socketJs连接成功。。。。。。。。。。。。");
            //开启定时发送测试数据
            if (subscribe) {
              //判断当前model是回调函数，还是数据对象
              if (typeof subscribe.model == 'function'){
                that.subscribe(subscribe.topic, subscribe.model, header)
              }else {
                that.subscribeToModel(subscribe.topic, subscribe.model, header)
              }
            }
          }, (err) => {
            that.connect = false;
            // 连接失败的回调
            that.reconnect(subscribe, header)
          });
        }
      },
      /**
       * 从连socket服务
       * @param socketUrl 链接访问路径地址
       * @param header 重新链接请求头
       */
      reconnect (subscribe, header) {
        let that = this;
        const timer = setInterval(() => {
          that.socket = new SockJS(that.url + that.baseUrl)
          that.stompClient = Stomp.over(this.socket)
          //去掉debugger模式
          that.stompClient.debug = null;
          that.stompClient.connect(header ? header : {}, (frame) => {
            that.connect = true;
            console.log("当前socketJs连接成功。。。。。。。。。。。。");
            if (subscribe) {
              //判断当前model是回调函数，还是数据对象
              if (typeof subscribe.model == 'function'){
                that.subscribe(subscribe.topic, subscribe.model, header)
              }else {
                that.subscribeToModel(subscribe.topic, subscribe.model, header)
              }
            }
            clearInterval(timer)
          }, err => {
            that.connect = false;
            console.log('Reconnect failed！');
          })
        }, 1000);
      },

      /***
       * 订阅指定主题消息内容
       * @param topic 主题
       * @param callback 回调函数
       */
      subscribe(topic, callback, header) {
        let subscribe = {
          topic: topic,
          model: callback
        }
        if (this.connect) {
          try {
            let sub = this.stompClient.subscribe(topic, (msg) => {
              callback(msg.body);
            }, header);
            if (!this.subscriber[topic]) {
              this.subscriber[topic] = {}
            }
            this.subscriber[topic] = sub;
          }catch (err){
            console.log("当前socketJs未连接，请先执行链接connection()函数。");
            this.connection(subscribe, header);
          }
        } else {
          console.log("当前socketJs未连接，请先执行connection()函数。");
          this.connection(subscribe, header);
        }
      },

      /**
       * 订阅指定主题消息，根据模块标识，来区分模块和对应需要执行的函数句柄。
       * @param topic 主题名
       * @param model 模块标识和更新语句柄=======>key：模块标识，value：标识对应的函数句柄或则回调函数，如：{modelName1：(msg) => {}, modelName2: (msg) => {}}
       * @param header
       */
      subscribeToModel(topic, model, header) {
        //组装订阅参数
        let subscribe = {
          topic: topic,
          model: model
        }
        //判断当前链接是否正常可用，如果不可用则重新进行链接
        if (this.connect) {
          //判断当前model是否和历史缓存一致，如果不一致则覆盖更新
          if (this.smallModel[topic] !== model) {
            this.smallModel[topic] = model;
          }
          header = header || {};
          //模块标识集合，用于下发订阅参数
          let modularFlag = [];
          //将模块标识在model中提取出来，并放入订阅header中。
          Object.keys(model).forEach(key => { modularFlag.push(key) });
          //将订阅参数，放置于header头部，携带到后台管理
          if (!header.modularFlag){
            header.modularFlag = []
          }
          header.modularFlag = modularFlag;
          //发布订阅请求
          try {
            let sub = this.stompClient.subscribe(topic, (msg) => {
              console.log("主题【"+topic+"】订阅成功");
              let content = JSON.parse(msg.body);
              //判断当前订阅消息内容是否为数组，如果是数组则遍历执行
              if (Array.isArray(content)) {
                for (let item of content) {
                  this.smallModel[topic][item.topic](item.msg);
                }
              } else {
                this.smallModel[topic][content.topic](content.msg);
              }
            }, header)
            if (!this.subscriber[topic]) {
              this.subscriber[topic] = {}
            }
            this.subscriber[topic] = sub
          }catch (err){
            console.log("当前socketJs未连接，请先执行链接connection()函数。");
            this.connection(subscribe, header);
          }
        } else {
          console.log("当前socketJs未连接，请先执行connection()函数。");
          this.connection(subscribe, header);
        }
      },

      /**
       * 取消指定topic订阅监听者
       * @param topic
       */
      unSubscribe(topic) {
        let subscriber = this.subscriber[topic];
        if (!subscriber) {
          return;
        }
        subscriber.unsubscribe();
        delete this.smallModel[topic];
        delete this.subscriber[topic];
        console.log("取消主题：【" + topic + "】订阅")
      },

      /**
       * 断开连接
       */
      disconnect() {
        if (this.stompClient != null && this.socket != null) {
          let that = this;
          //断开连接
          this.stompClient.disconnect(() => {
            //清除订阅者，//清楚订阅模块
            Object.keys(that.subscriber).forEach(key => {
              delete that.subscriber[key];
              delete that.smallModel[key];
            });
            //清除ws对象
            websocket.removeWs(that);
            that.stompClient = null;
            that.socket = null;
            that.connect = false;
            console.log("socketJs断开连接成功。");
          });
        }
      },
      /**
       * 发送消息
       * @param url 请求路径
       * @param param 请求参数
       * @param header 请求头内容，可选。
       */
      sendMsg(url, param, header) {
        this.stompClient.send(url, header ? header : {}, param ? JSON.stringify(param) : "ping");
      },
    }
    websocket.addWs(ws);
    return ws;
  },
}
export default websocket;
